package yawning.mew.equipment;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import yawning.mew.equipment.RawItem.Property;
import yawning.mew.util.BattleNetAuth;

public class XMLItemSource extends ItemSource {
	private static final String ITEM_ID_FILE = "resources/ItemIDs.conf";
	private static final String XML_ITEMS_FILE = "/META-INF/items.xml";
	private static Map<Integer,RawItem> sItemsById = new HashMap<Integer,RawItem>();

	private static final String ELEMENT_ROOT = "items";
	private static final String ELEMENT_ITEM = "item";
	private static final String ATTR_NAME = "name";
	private static final String ATTR_ITEMID = "itemId";
	private static final String ATTR_ICON = "icon";

	@SuppressWarnings("serial")
	private static final Map<String, Property> sIntPropertiesByAttr = new HashMap<String, Property>() {{
		put("slot", Property.SLOT);
		put("armor", Property.ARMOR);
		put("quality", Property.QUALITY);
		put("class", Property.CLASS);
		put("subclass", Property.SUBCLASS);
		put("itemSet", Property.ITEM_SET);
		put("itemLevel", Property.ITEM_LEVEL);

		put("stamina", Property.STAMINA);
		put("agility", Property.AGILITY);
		put("strength", Property.STRENGTH);
		put("intellect", Property.INTELLECT);
		put("spirit", Property.SPIRIT);

		put("spellPower", Property.SPELL_POWER);
		put("attackPower", Property.ATTACK_POWER);
		put("hasteRating", Property.HASTE_RATING);
		put("critRating", Property.CRIT_RATING);
		put("expertiseRating", Property.EXPERTISE_RATING);
		put("hitRating", Property.HIT_RATING);
		put("dodgeRating", Property.DODGE_RATING);
		put("masteryRating", Property.MASTERY_RATING);
		put("parryRating", Property.PARRY_RATING);
		put("resilienceRating", Property.RESILIENCE_RATING);
		put("bonusArmor", Property.BONUS_ARMOR);

		put("numSockets", Property.NUM_SOCKETS);
		put("socket1", Property.SOCKET1);
		put("socket2", Property.SOCKET2);
		put("socket3", Property.SOCKET3);
		put("socketBonusStat", Property.SOCKET_BONUS_STAT);
		put("socketBonusValue", Property.SOCKET_BONUS_VALUE);

		put("meleeDmgMin", Property.MELEE_DMG_MIN);
		put("meleeDmgMax", Property.MELEE_DMG_MAX);

		put("rangedDmgMin", Property.RANGED_DMG_MIN);
		put("rangedDmgMax", Property.RANGED_DMG_MAX);
	}};

	@SuppressWarnings("serial")
	private static final Map<String, Property> sDoublePropertiesByAttr = new HashMap<String, Property>() {{
		put("meleeDps", Property.MELEE_DPS);
		put("meleeSpeed", Property.MELEE_SPEED);

		put("rangedDps", Property.RANGED_DPS);
		put("rangedSpeed", Property.RANGED_SPEED);
	}};

	private static void loadItems() {
		synchronized (sItemsById) {
			if (sItemsById.size() > 0) return;

			try {
				SAXBuilder builder = new SAXBuilder();
				InputStream is = XMLItemSource.class.getResourceAsStream(XML_ITEMS_FILE);
				Document doc = builder.build(is);
				Element root = doc.getRootElement();

				@SuppressWarnings("unchecked") List<Element> items = root.getChildren(ELEMENT_ITEM);
				for (Element e : items) {
					RawItem item = new RawItem();
					item.name = e.getAttributeValue(ATTR_NAME);
					item.id = Integer.parseInt(e.getAttributeValue(ATTR_ITEMID));
					item.icon = e.getAttributeValue(ATTR_ICON);

					for (Map.Entry<String,Property> entry : sIntPropertiesByAttr.entrySet()) {
						String attr = entry.getKey();
						Property property = entry.getValue();

						String val = e.getAttributeValue(attr);
						if (val != null) item.properties.put(property, Integer.parseInt(val));
					}

					for (Map.Entry<String,Property> entry : sDoublePropertiesByAttr.entrySet()) {
						String attr = entry.getKey();
						Property property = entry.getValue();

						String val = e.getAttributeValue(attr);
						if (val != null) item.properties.put(property, Double.parseDouble(val));
					}

					sItemsById.put(item.id, item);
				}

			}
			catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public RawItem getById(int id) {
		// Ensure that our backing store is loaded.
		loadItems();

		RawItem item = sItemsById.get(id);
		if (item != null) {
			return item;
		}

		return (mNext != null) ? mNext.getById(id) : null;
	}

	private static void reloadItems() {
//		ItemSource.pushBack(new ArmoryItemSource());
		ItemSource.pushBack(new WowheadItemSource());	// TODO: Remove this once ArmoryItemSource() works.

		try {
			List<Integer> itemIdList = new LinkedList<Integer>();
			BufferedReader br = new BufferedReader(new FileReader(ITEM_ID_FILE));

			try {
				String line;
				while ((line = br.readLine()) != null) {
					// Strip out comments.
					int idx = line.indexOf('#');
					String s = (idx != -1) ? new String(line.substring(0, idx)) : line;

					try {
						itemIdList.add(Integer.parseInt(s.trim()));
					} catch (Exception e) {
						// Huh, couldn't figure out the item id.  Whatever.
					}
				}
			} finally {
				br.close();
			}

			// Prune duplicate item IDs.
			LinkedHashSet<Integer> itemIdSet = new LinkedHashSet<Integer>();
			for (int itemId: itemIdList) {
				if (itemIdSet.contains(itemId)) {
					System.out.println(" * Duplicate item id found " + itemId);
				} else
					itemIdSet.add(itemId);
			}

			List<RawItem> items = new LinkedList<RawItem>();
			for (int itemId : itemIdSet) {
				try {
					RawItem item = ItemSource.getItem(itemId);
					items.add(item);

					System.out.println(item.toString());
				} catch (Exception e) {
					// Errors are non-fatal.
					System.out.println(" * Failed to import data for item " + itemId);
					throw new RuntimeException(e);
				}
			}

			Map<Property,String> intAttrsByProperty = new HashMap<Property,String>();
			for (Entry<String, Property> entry : sIntPropertiesByAttr.entrySet()) {
				intAttrsByProperty.put(entry.getValue(), entry.getKey());
			}
			Map<Property,String> doubleAttrsByProperty = new HashMap<Property,String>();
			for (Entry<String,Property> entry : sDoublePropertiesByAttr.entrySet()) {
				doubleAttrsByProperty.put(entry.getValue(), entry.getKey());
			}

			Element root = new Element(ELEMENT_ROOT);
			for (RawItem item : items) {
				Element ele = new Element(ELEMENT_ITEM);

				ele.setAttribute(ATTR_NAME, item.name);
				ele.setAttribute(ATTR_ITEMID, Integer.toString(item.id));
				ele.setAttribute(ATTR_ICON, item.icon);

				for (Map.Entry<Property,Number> entry : item.properties.entrySet()) {
					Property prop = entry.getKey();
					Number val = entry.getValue();

					if (intAttrsByProperty.containsKey(prop)) {
						ele.setAttribute(intAttrsByProperty.get(prop), Integer.toString(val.intValue()));
					} else if (doubleAttrsByProperty.containsKey(prop)) {
						ele.setAttribute(doubleAttrsByProperty.get(prop), Double.toString(val.doubleValue()));
					} else {
						System.out.println(" * Unsupported property " + prop + " (" + item.id + ")");
					}
				}

				root.addContent(ele);
			}

			// Dump it out to a file.
			XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
//			out.output(new Document(root), new FileOutputStream(XML_ITEMS_FILE));
			out.output(new Document(root), new FileOutputStream("items.xml"));

			System.out.println();
			System.out.println("Regeneration complete.");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String args[]) {
		// Optionally try to load the public/private key pair for authentication.
		BattleNetAuth auth = BattleNetAuth.getInstance();
		auth.loadDefaultKeys();

		reloadItems();
	}
}
